using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.IO;
using MacomberMap.Common.Internals;
using System.Xml;
using MacomberMap.Common.Components;
using System.Windows.Forms;

namespace MacomberMap.Common.Database_Interface.CIM.CIM_Interface
{
    /// <summary>
    /// This class is responsible for handling the opening of a CIM file
    /// </summary>
    public class CIM_File_Reader : CIM_File_Interface
    {
        #region Variable Declarations
        /// <summary>Our flags of a chunk's load completion</summary>
        private ManualResetEvent[] ResetEvents;

        /// <summary>The positions for each of the file chunks</summary>
        private long[] Positions;

        /// <summary>The size of the file</summary>
        private long _FileSize;

        /// <summary>The thread responsible for managing the CIM loading process</summary>
        private Thread CIMLoaderThread;

        /// <summary>Whether to force single-threaded mode</summary>
        public static DialogResult SingleThreaded = DialogResult.None;
        #endregion

        #region Initialization
        /// <summary>
        /// Open a new CIM XML file, and parse accordingly
        /// </summary>
        /// <param name="FileName">The file to be read</param>
        /// <param name="BlockSize">How large our blocksize should be for reading</param>
        public CIM_File_Reader(String FileName, long BlockSize)
        {                 
     
            //Create our manager thread
            CIMLoaderThread = new Thread(new ParameterizedThreadStart(CIMFileReaderManager));
            CIMLoaderThread.Name = "CIM File Reader Manager";
            CIMLoaderThread.SetApartmentState(ApartmentState.MTA);
            CIMLoaderThread.Start(new object[] { FileName, BlockSize });
        }


        private void CIMFileReaderManager(Object Parameters)
        {
            //Pull out our variables
            this.StartTime = DateTime.Now;
            String FileName = (String)(Parameters as Object[])[0];
            long BlockSize = (long)(Parameters as Object[])[1];


            //Determine our file size
            FileInfo fI = new FileInfo(FileName);
            _FileSize =  fI.Length;
            DateTime StartTime = DateTime.Now;

            //Always Run Single Threaded for Now
            SingleThreaded = DialogResult.Yes;

            if (SingleThreaded == DialogResult.Yes || MessageBox.Show("Run in single-threaded mode?", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
            {
                Positions = new long[1];
                ResetEvents = new ManualResetEvent[1];
                ResetEvents[0] = new ManualResetEvent(false);
                CIMReaderUsingXml(new long[] { 0, new FileInfo(FileName).Length, 0 });
                //LoadChunk(new long[] { 0, new FileInfo(FileName).Length, 0 });
                BuildElementsByType();
                BuildTypeToTypes();
                CIM_Index_Writer.WriteIndex(fI.DirectoryName + "\\" + Path.GetFileNameWithoutExtension(fI.Name) + ".CimIndex");
                Completed = true;
            }
            else
            {
                //Split up our file into appropriately sized chunks, and load it in.
                int NumChunks = (int)Math.Ceiling((Decimal)_FileSize / (Decimal)BlockSize);
                ResetEvents = new ManualResetEvent[NumChunks];
                Positions = new long[NumChunks];
                for (int a = 0; a < NumChunks; a++)
                {
                    ResetEvents[a] = new ManualResetEvent(false);
                    Positions[a] = 0;                              
                    ThreadPool.QueueUserWorkItem(new WaitCallback(CIMReaderUsingXml), new long[] { BlockSize * a, (BlockSize * (a + 1) - 1), a });
                    //ThreadPool.QueueUserWorkItem(new WaitCallback(LoadChunk), new long[] { BlockSize * a, (BlockSize * (a + 1) - 1), a });
                }
                WaitHandle.WaitAll(ResetEvents);
                foreach (Components.CIM_Element Elem in MM_Database_CIM.Elements.Values)
                    Elem._InLinks.TrimExcess();                
                BuildElementsByType();
                BuildTypeToTypes();
                CIM_Index_Writer.WriteIndex(fI.DirectoryName + "\\" + fI.Name + ".CimIndex");
                Completed = true;
            }            
        }

        #region Parsing through XML
        /// <summary>
        /// Use an XmlTextReader to parse the file.
        /// </summary>
        /// <param name="BlockParameters">Our block of parameters</param>
        private void CIMReaderUsingXml(Object BlockParameters)
        {
            //Pull out our variables
            //Load our parameters for this chunk to be processed, and open the file
            long StartPosition = (BlockParameters as long[])[0];
            long EndPosition = (BlockParameters as long[])[1];
            int CurChunk = (int)(BlockParameters as long[])[2];
            int NumElements = 0;

            //Open our reader            
            FileStream XmlStream = new FileStream(MM_Database_CIM.CIMFileName, FileMode.Open, FileAccess.Read, FileShare.Read);
            XmlStream.Position = StartPosition;

            //Now, keep searching through the file until we've found our first rdf:ID
            byte[] inByte = new byte[MM_Configuration.FileBufferSize];

            long StartElementPosition, EndElementPosition;
            int StartElement, EndElement;
            String ElemType, inLine;
            int rdfLocation, NumBytes;

            while (XmlStream.Position < EndPosition)
            {
                //Read a chunk in
                NumBytes = XmlStream.Read(inByte, 0, inByte.Length);
                inLine = Encoding.ASCII.GetString(inByte, 0, NumBytes);

                //If we have our rdf:ID tag, dial back to the beginning of the element, and process accordingly
                if ((rdfLocation = inLine.IndexOf("rdf:ID")) != -1)
                {
                    StartElement = inLine.LastIndexOf('<', rdfLocation);
                    StartElementPosition = XmlStream.Position + StartElement - NumBytes;
                    ElemType = inLine.Substring(StartElement + 1, rdfLocation - StartElement - 1).Trim();

                    //Now, locate the end of the element
                    while ((EndElement = inLine.IndexOf("</" + ElemType + ">", StartElement)) == -1)
                    {
                        //Dial back to the last element close, and read the next chunk
                        XmlStream.Seek(inLine.LastIndexOf('>') - NumBytes, SeekOrigin.Current);
                        NumBytes = XmlStream.Read(inByte, 0, inByte.Length);
                        inLine = Encoding.ASCII.GetString(inByte, 0, NumBytes);
                        StartElement = 0;
                    }

                    //Determine our end position, and process our element
                    EndElementPosition = (XmlStream.Position - NumBytes) + ElemType.Length + 3 + EndElement;
                    int ElemLength = (int)(EndElementPosition - StartElementPosition);
                  
                    ProcessElement(XmlStream, StartElementPosition, ElemLength , ElemType);
                    NumElements++;
                    Positions[CurChunk] = XmlStream.Position - StartPosition;
                }
            }
            Positions[CurChunk] = EndPosition - StartPosition;
            ResetEvents[CurChunk].Set();
        }
        
        
        /// <summary>
        /// Process an XML element
        /// </summary>
        /// <param name="XmlStream">The XML File Stream</param>
        /// <param name="Start">The starting position of the XML</param>
        /// <param name="Length">The length of the XML</param>
        /// <param name="ElementType">The element type of the XML</param>
        private void ProcessElement(FileStream XmlStream, long Start, int Length, String ElementType)
        {
            XmlStream.Position = Start;
            Byte[] inBytes = new byte[Length];
            int NumBytes = XmlStream.Read(inBytes, 0, Length);
            String inLine = Encoding.ASCII.GetString(inBytes, 0, NumBytes);
            
            //Determine our full rdf:ID
            int rdfLocation = inLine.IndexOf("rdf:ID");
            int rdfIDEnd = inLine.IndexOf('\"', rdfLocation + 8);
            String rdfID = inLine.Substring(rdfLocation + 8, rdfIDEnd - rdfLocation - 8);            
            //CIM_Element FoundElement = MM_Repository.AddCIMElement(rdfID, Start, Length, ElementType, inLine);
            MM_Database_CIM.AddCIMElement(rdfID, Start, Length, ElementType, inLine)._OutLinks.TrimExcess();
        }
        #endregion

        


        #endregion

        #region Interfaces
        /// <summary>
        /// The total file size of the CIM file
        /// </summary>
        public override long FileSize
        {
            get { return _FileSize; }
        }

        /// <summary>
        /// The overall position across all threads
        /// </summary>
        public override long Position
        {
            get
            {
                long Tally = 0;
                if (Positions != null)
                    foreach (long AddMe in Positions)
                        Tally += AddMe;
                return Tally;
            }
        }
        #endregion
    }

}
